package ru.papergames.model.binsocket
{
	import by.andronix.logger.Logger;
	
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.Socket;
	
	import ru.papergames.model.binsocket.data.ExtByteArray;
	import ru.papergames.model.binsocket.events.SocketConnectionEvent;
	
	
	/**
	 * Manager to handle socket data communications
	 * 
	 * 
	 * 
	 * */
	public class SocketDataManager extends EventDispatcher
	{	
		
		private var socket:Socket;
		private var policyPacketRecieved:Boolean = false;
		private var policyStrAccumulator:String = '';
		private var queue:ExtByteArray = new ExtByteArray();
		
		private var server:String;
		private var port:uint;
						
		public function initialize(server:String, port:uint, timeout:uint = 5000):void {
			if (!socket) socket = new Socket();	
			
			this.server = server;		
			this.port = port;
			
			socket.timeout = timeout;
			
			queue.cleanup();
						
			socket.addEventListener(Event.CLOSE, onClose);
			socket.addEventListener(Event.CONNECT, onConnect);
			socket.addEventListener(ProgressEvent.SOCKET_DATA, onData);
			socket.addEventListener(IOErrorEvent.IO_ERROR, onError);
			socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
			
			policyPacketRecieved = false;
			policyStrAccumulator = '';
			
			socket.connect(server, port);
		}		
		
		public function get connected():Boolean {
			return socket ? socket.connected : false;
		}
		
		public function disconnect():void {
			if (socket && socket.connected) {
				socket.close();
				onClose(null);
				queue.cleanup();
			}
		}
		
		public function sendData(data:ExtByteArray):void {
			if (data) {
				socket.writeBytes(data);
				socket.flush();
			}
		}
		
		private function onConnect(evt:Event):void {
			queue.cleanup();			
		}
		
		private function onClose(evt:Event):void {
			this.dispatchEvent(new SocketConnectionEvent(SocketConnectionEvent.SOCKET_CLOSED));
		}
		
		private function onData(evt:ProgressEvent):void {
			//collect packet 
			var recievedBytes:ExtByteArray = new ExtByteArray();
			
			socket.readBytes(recievedBytes);
			
			Logger.getLogger(this).debug( "[" + [server, port].join(':') + "]: " + recievedBytes.toString() );
			
			var packets:Array = addDataInQueueAndPopPackets(recievedBytes);
						
			for each (var packet:ExtByteArray in packets) {
				if (packet && packet.length > 0) this.dispatchEvent(new SocketConnectionEvent(SocketConnectionEvent.SOCKET_DATA, packet));
			}
			
		}
		
		private function onError(evt:ErrorEvent):void {
			this.dispatchEvent(new SocketConnectionEvent(SocketConnectionEvent.SOCKET_ERROR, evt.text));
		}
		
		
		private function addDataInQueueAndPopPackets(rcvData:ExtByteArray):Array {
								
			//append bytes to tail of queue
			queue.position = queue.length;						
			queue.writeBytes(rcvData);
			
			queue.position = 0;
			
			//analyzing
			
			//ignore policy bytes!!!	
			
			//start from - 3C 63 72 6F 73 73 2D 64 6F 6D 61 69 6E 2D 70 6F 6C 69 63 79 3E ... 
			//tail as ... 3C 2F 63 72 6F 73 73 2D 64 6F 6D 61 69 6E 2D 70 6F 6C 69 63 79 3E 00
			
			
			if (!policyPacketRecieved) {
				for (var i:int = 0; i < queue.length; i++) {
					queue.position = i;
					var byte:uint = queue.readUnsignedByte();
					policyStrAccumulator += String.fromCharCode(byte);
					if (byte == 0) break;
				}
				
				policyPacketRecieved = policyStrAccumulator.search(/^<cross-domain-policy>.+<\/cross-domain-policy>\x00/is) != -1;
				
				if (policyPacketRecieved) {
					Logger.getLogger(this).info('Policy Packet recieved!');
					//strip policy from queue
					//start from queue.position+1
					var readPos:int = queue.position + 1;
					
					if (readPos >= queue.length - 1) {
						queue.cleanup();
					} else {
						var queueRemainder:ExtByteArray = new ExtByteArray();
						queue.readBytes(queueRemainder, readPos);
						queue = queueRemainder;
					}
					
					this.dispatchEvent(new SocketConnectionEvent(SocketConnectionEvent.SOCKET_CONNECTED));					
				}
				
			} 
			
			var packets:Array = [];

			if (policyPacketRecieved && queue.length > 2) {
				
				//packet structure = 
				//0x00 0x00 - size
				//etc
				
				queue.position = 0;				
									
				while (queue.bytesAvailable > 2) {
					
					var packet:ExtByteArray = getPacket(queue);
					if (packet) {
						packets.push(packet);
					} else break;
					
				}
				
				//shift queue
				if (queue.bytesAvailable > 0) {
					var shiftedQueue:ExtByteArray = new ExtByteArray();
					queue.readBytes(shiftedQueue);
					queue = shiftedQueue;
				} else {
					queue.cleanup();
				}
				
			}
			
			
			return packets;
		}
		
		
		private function getPacket(bytes:ExtByteArray):ExtByteArray {
			
			var initPos:uint = bytes.position;
			
			//size without header
			var packetSize:uint = bytes.readUnsignedShort() - 2;
					
			if (packetSize > bytes.bytesAvailable) {
				//packet isn't fully loaded
				bytes.position = initPos;
				return null;
			} else {
				var retBytes:ExtByteArray = new ExtByteArray();
				
				try {
				
					bytes.readBytes(retBytes, 0, packetSize);
				
				} catch (e:Error) {
					
					dispatchEvent(
						new SocketConnectionEvent(SocketConnectionEvent.SOCKET_FAULT_DATA, bytes.toString())
					);
					
					//flush queue on error???
					
					bytes.position = initPos;
					return null;
					
				}
				
				return retBytes;
			}
		}
		

	}
}